/********************************************************************************
Copyright (C) 2016 Marvell International Ltd.

SPDX-License-Identifier: BSD-2-Clause-Patent

*******************************************************************************/

#include <Protocol/DriverBinding.h>
#include <Protocol/I2cIo.h>
#include <Protocol/I2cDemo.h>
#include <Protocol/I2c.h>

#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/IoLib.h>
#include <Library/DebugLib.h>
#include <Library/PcdLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>

#include <Pi/PiI2c.h>

#include "I2cDemoDxe.h"

STATIC CONST EFI_GUID  I2cGuid = I2C_GUID;

EFI_DRIVER_BINDING_PROTOCOL  gDriverBindingProtocol = {
  I2cDemoSupported,
  I2cDemoStart,
  I2cDemoStop
};

EFI_STATUS
EFIAPI
I2cDemoSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  EFI_STATUS           Status = EFI_UNSUPPORTED;
  EFI_I2C_IO_PROTOCOL  *TmpI2cIo;
  UINT8                *I2cDemoAddresses;
  UINT8                *I2cDemoBuses;
  UINTN                i;

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiI2cIoProtocolGuid,
                  (VOID **)&TmpI2cIo,
                  gImageHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "I2cDemoSupported Error status: %d\n", Status));
    return EFI_UNSUPPORTED;
  }

  /* get I2CDEMO devices' addresses from PCD */
  I2cDemoAddresses = PcdGetPtr (PcdI2cDemoAddresses);
  I2cDemoBuses     = PcdGetPtr (PcdI2cDemoBuses);
  if (I2cDemoAddresses == 0) {
    Status = EFI_UNSUPPORTED;
    DEBUG ((DEBUG_INFO, "I2cDemoSupported: I2C device found, but it's not I2CDEMO\n"));
    goto out;
  }

  Status = EFI_UNSUPPORTED;
  for (i = 0; I2cDemoAddresses[i] != '\0'; i++) {
    /* I2C guid must fit and valid DeviceIndex must be provided */
    if (CompareGuid (TmpI2cIo->DeviceGuid, &I2cGuid) &&
        (TmpI2cIo->DeviceIndex == I2C_DEVICE_INDEX (
                                    I2cDemoBuses[i],
                                    I2cDemoAddresses[i]
                                    )))
    {
      DEBUG ((DEBUG_INFO, "I2cDemoSupported: attached to I2CDEMO device\n"));
      Status = EFI_SUCCESS;
      break;
    }
  }

out:
  gBS->CloseProtocol (
         ControllerHandle,
         &gEfiI2cIoProtocolGuid,
         gImageHandle,
         ControllerHandle
         );

  return Status;
}

EFI_STATUS
EFIAPI
I2cDemoRead (
  IN CONST ROCKCHIP_I2CDEMO_PROTOCOL  *This,
  IN UINT8                            *RegAddress,
  IN UINT16                           RegAddressLength,
  IN UINT8                            *Buffer,
  IN UINT16                           Length
  )
{
  EFI_I2C_REQUEST_PACKET  *RequestPacket;
  UINTN                   RequestPacketSize;
  EFI_STATUS              Status          = EFI_SUCCESS;
  I2CDEMO_CONTEXT         *I2cDemoContext = I2CDEMO_SC_FROM_I2CDEMO (This);

  ASSERT (I2cDemoContext != NULL);
  ASSERT (I2cDemoContext->I2cIo != NULL);

  RequestPacketSize = sizeof (UINTN) + sizeof (EFI_I2C_OPERATION) * 2;
  RequestPacket     = AllocateZeroPool (RequestPacketSize);
  if (RequestPacket == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  RequestPacket->OperationCount = 2;

  RequestPacket->Operation[0].Flags         = 0;
  RequestPacket->Operation[0].LengthInBytes = RegAddressLength;
  RequestPacket->Operation[0].Buffer        = RegAddress;

  RequestPacket->Operation[1].Flags         = I2C_FLAG_READ;
  RequestPacket->Operation[1].LengthInBytes = Length;
  RequestPacket->Operation[1].Buffer        = Buffer;

  Status = I2cDemoContext->I2cIo->QueueRequest (I2cDemoContext->I2cIo, 0, NULL, RequestPacket, NULL);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "I2cDemoTransfer: error %d during transmission\n", Status));
  }

  FreePool (RequestPacket);

  return Status;
}

EFI_STATUS
EFIAPI
I2cDemoWrite (
  IN CONST ROCKCHIP_I2CDEMO_PROTOCOL  *This,
  IN UINT8                            *RegAddress,
  IN UINT16                           RegAddressLength,
  IN UINT8                            *Buffer,
  IN UINT16                           Length
  )
{
  EFI_I2C_REQUEST_PACKET  *RequestPacket;
  UINTN                   RequestPacketSize;
  EFI_STATUS              Status          = EFI_SUCCESS;
  I2CDEMO_CONTEXT         *I2cDemoContext = I2CDEMO_SC_FROM_I2CDEMO (This);
  UINT8                   *Data;
  UINT16                  i;

  ASSERT (I2cDemoContext != NULL);
  ASSERT (I2cDemoContext->I2cIo != NULL);

  RequestPacketSize = sizeof (UINTN) + sizeof (EFI_I2C_OPERATION);
  RequestPacket     = AllocateZeroPool (RequestPacketSize);
  if (RequestPacket == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Data = AllocateZeroPool (RegAddressLength + Length);
  if (Data == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  for (i = 0; i < RegAddressLength; i++) {
    Data[i] = RegAddress[i]  & 0xff;
  }

  for (i = RegAddressLength; i < RegAddressLength + Length; i++) {
    Data[i] = Buffer[i - RegAddressLength]  & 0xff;
  }

  RequestPacket->OperationCount = 1;

  RequestPacket->Operation[0].Flags         = 0;
  RequestPacket->Operation[0].LengthInBytes = RegAddressLength + Length;
  RequestPacket->Operation[0].Buffer        = Data;

  Status = I2cDemoContext->I2cIo->QueueRequest (I2cDemoContext->I2cIo, 0, NULL, RequestPacket, NULL);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "I2cDemoTransfer: error %d during transmission\n", Status));
  }

  FreePool (Data);
  FreePool (RequestPacket);

  return Status;
}

EFI_STATUS
EFIAPI
I2cDemoStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  EFI_STATUS       Status = EFI_SUCCESS;
  I2CDEMO_CONTEXT  *I2cDemoContext;

  I2cDemoContext = AllocateZeroPool (sizeof (I2CDEMO_CONTEXT));
  if (I2cDemoContext == NULL) {
    DEBUG ((DEBUG_ERROR, "I2cDemo: allocation fail\n"));
    return EFI_OUT_OF_RESOURCES;
  }

  I2cDemoContext->ControllerHandle      = ControllerHandle;
  I2cDemoContext->Signature             = I2CDEMO_SIGNATURE;
  I2cDemoContext->I2cDemoProtocol.Read  = I2cDemoRead;
  I2cDemoContext->I2cDemoProtocol.Write = I2cDemoWrite;

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiI2cIoProtocolGuid,
                  (VOID **)&I2cDemoContext->I2cIo,
                  gImageHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "I2cDemo: failed to open I2cIo\n"));
    FreePool (I2cDemoContext);
    return EFI_UNSUPPORTED;
  }

  I2cDemoContext->I2cDemoProtocol.Identifier = I2cDemoContext->I2cIo->DeviceIndex;
  Status                                     = gBS->InstallMultipleProtocolInterfaces (
                                                      &ControllerHandle,
                                                      &gRockchipI2cDemoProtocolGuid,
                                                      &I2cDemoContext->I2cDemoProtocol,
                                                      NULL
                                                      );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "I2cDemo: failed to install I2CDEMO protocol\n"));
    goto fail;
  }

  return Status;

fail:
  FreePool (I2cDemoContext);
  gBS->CloseProtocol (
         ControllerHandle,
         &gEfiI2cIoProtocolGuid,
         gImageHandle,
         ControllerHandle
         );

  return Status;
}

EFI_STATUS
EFIAPI
I2cDemoStop (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN  EFI_HANDLE                  ControllerHandle,
  IN  UINTN                       NumberOfChildren,
  IN  EFI_HANDLE                  *ChildHandleBuffer OPTIONAL
  )
{
  ROCKCHIP_I2CDEMO_PROTOCOL  *I2cDemoProtocol;
  EFI_STATUS                 Status;
  I2CDEMO_CONTEXT            *I2cDemoContext;

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gRockchipI2cDemoProtocolGuid,
                  (VOID **)&I2cDemoProtocol,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );

  if (EFI_ERROR (Status)) {
    return EFI_DEVICE_ERROR;
  }

  I2cDemoContext = I2CDEMO_SC_FROM_I2CDEMO (I2cDemoProtocol);

  gBS->UninstallMultipleProtocolInterfaces (
         &ControllerHandle,
         &gRockchipI2cDemoProtocolGuid,
         &I2cDemoContext->I2cDemoProtocol,
         &gEfiDriverBindingProtocolGuid,
         &gDriverBindingProtocol,
         NULL
         );
  gBS->CloseProtocol (
         ControllerHandle,
         &gEfiI2cIoProtocolGuid,
         gImageHandle,
         ControllerHandle
         );
  FreePool (I2cDemoContext);
  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
I2cDemoInitialise (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;

  Status = gBS->InstallMultipleProtocolInterfaces (
                  &ImageHandle,
                  &gEfiDriverBindingProtocolGuid,
                  &gDriverBindingProtocol,
                  NULL
                  );

  return Status;
}
